package top.go2do.auth.business.user.service;

import jakarta.persistence.criteria.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import top.go2do.common.dao.ApplicationRepository;
import top.go2do.common.module.ApplicationPO;
import top.go2do.auth.business.captcha.service.CaptchaService;
import top.go2do.auth.business.role.dao.RoleRepository;
import top.go2do.auth.business.role.module.RolePO;
import top.go2do.auth.business.role.module.RoleSimplyVO;
import top.go2do.common.dao.UserRepository;
import top.go2do.auth.business.user.dao.UserRoleRepository;
import top.go2do.auth.business.user.enumeration.UserStatus;
import top.go2do.auth.business.user.module.*;
import top.go2do.auth.configProperties.AuthModuleConfig;
import top.go2do.auth.exception.AuthErrorCode;
import top.go2do.auth.exception.AuthException;
import top.go2do.common.module.UserInfoBO;
import top.go2do.common.module.UserPO;
import top.go2do.common.module.pageQuery.PageResult;
import top.go2do.common.resolver.TokenUserInfoHandler;
import top.go2do.common.utils.CryptUtils;
import top.go2do.common.utils.IdUtils;

import java.util.*;
import java.util.stream.Collectors;

import static top.go2do.common.redis.RedisKey.getTokenUserInfoKey;

/**
 * @author xingyuji
 * @date 2025/3/20 15:25
 * @description
 */
@Slf4j
@Service
public class UserServiceImpl implements UserService, TokenUserInfoHandler {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private IdUtils idUtils;

    @Autowired
    private AuthModuleConfig authConfig;

    @Autowired
    private CaptchaService captchaService;

    @Autowired
    private ApplicationRepository applicationRepository;

    @Autowired
    private UserRoleRepository userRoleRepository;

    @Autowired
    private RoleRepository roleRepository;

    @Override
    public void createUser(UserAddBO addBO) {
        UserPO userPO = new UserPO();
        Long userId = idUtils.getSnowflakeId();
        BeanUtils.copyProperties(addBO, userPO);
        userPO.setUserId(userId);
        userPO.setStatus(UserStatus.NORMAL.getValue());
        userRepository.save(userPO);
    }

    @Override
    public String encryptRawPassword(String rawPassword) {
        return CryptUtils.encode(rawPassword);
    }

    @Override
    public UserPO findByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    @Override
    public UserPO findByAppIdAndUsername(String appId, String username) {
        UserPO userPO = userRepository.findByAppIdAndUsername(appId, username);
        if (userPO == null) {
            userPO =  userRepository.findByAppIdAndEmail(appId, username);
        }
        if (userPO == null) {
            userPO =  userRepository.findByAppIdAndPhone(appId, username);
        }
        return userPO;
    }

    @Override
    public boolean checkUsernameAndPassword(LoginCheckBO checkBO, UserPO userPO) {
        String username = checkBO.getUsername();
        if (userPO == null) {
            userPO = findByAppIdAndUsername(checkBO.getAppId(), username);
        }
        if (userPO == null) {
            log.warn("username {} not exist.", username);
            throw new AuthException(AuthErrorCode.USERNAME_NOT_EXIST);
        }
        if (UserStatus.LOCKED.equals(userPO.getStatus())) {
            log.warn("username {} locked.", username);
            throw new AuthException(AuthErrorCode.USER_LOCKED);
        }
        if (UserStatus.LOG_OFF.equals(userPO.getStatus())) {
            log.warn("username {} log off.", username);
            throw new AuthException(AuthErrorCode.USER_LOG_OFF);
        }
        String passwordHash = userPO.getPasswordHash();
        boolean checkPassword = CryptUtils.check(checkBO.getPasswordRaw(), passwordHash);
        return checkPassword;
    }

    @Override
    public boolean checkUsernameAndPassword(LoginCheckBO checkBO) {
        String username = checkBO.getUsername();
        UserPO userPO = findByUsername(username);
        return checkUsernameAndPassword(checkBO, userPO);
    }

    @Override
    public List<UserListVO> po2vo(List<UserPO> list) {

        Set<String> appIdSet = new HashSet<>();
        Set<Long> userIdSet = new HashSet<>();
        Set<Long> roleUserIdSet = new HashSet<>();

        for (UserPO po : list) {
            appIdSet.add(po.getAppId());
            userIdSet.add(po.getCreatedBy());
            userIdSet.add(po.getUpdatedBy());
            roleUserIdSet.add(po.getUserId());
        }
        List<ApplicationPO> appList = applicationRepository.findAllById(appIdSet);
        List<UserPO> userList = userRepository.findAllById(userIdSet);
        List<UserRolePO> userRoleList = userRoleRepository.findByUserIdIn(roleUserIdSet);
        Set<Long> roleIdSet;
        if (userRoleList != null) {
            roleIdSet = userRoleList.stream().map(UserRolePO::getRoleId).collect(Collectors.toSet());
        } else {
            roleIdSet = new HashSet<>();
        }
        List<RolePO> roleList = roleRepository.findAllById(roleIdSet);


        Map<String, String> appId2NameMap;
        Map<Long, String> userId2NameMap;
        Map<Long, RolePO> roleId2POMap;
        Map<Long, Set<RoleSimplyVO>> userId2RoleListMap;
        if (appList != null) {
            appId2NameMap = appList.stream().collect(Collectors.toMap(ApplicationPO::getAppId, ApplicationPO::getAppName));
        } else {
            appId2NameMap = new HashMap<>(0);
        }

        if (userList != null) {
            userId2NameMap = userList.stream().collect(Collectors.toMap(UserPO::getUserId, UserPO::getUsername));
        } else {
            userId2NameMap = new HashMap<>(0);
        }
        if (roleList != null) {
            roleId2POMap = roleList.stream().collect(Collectors.toMap(RolePO::getRoleId, po -> po));
        } else {
            roleId2POMap = new HashMap<>(0);
        }
        if (userRoleList != null) {
            userId2RoleListMap = new HashMap<>();
            for (UserRolePO userRolePO : userRoleList) {
                Long userId = userRolePO.getUserId();
                Long roleId = userRolePO.getRoleId();
                Set<RoleSimplyVO> roleSet = userId2RoleListMap.get(userId);
                if (roleSet == null) {
                    roleSet = new HashSet<>();
                }
                RolePO po = roleId2POMap.get(roleId);
                RoleSimplyVO roleVo = new RoleSimplyVO();
                roleVo.setRoleId(String.valueOf(po.getRoleId()));
                roleVo.setRoleName(po.getRoleName());
                roleVo.setRoleCode(po.getRoleCode());
                roleSet.add(roleVo);
                userId2RoleListMap.put(userId, roleSet);
            }
        } else {
            userId2RoleListMap = new HashMap<>(0);
        }


        List<UserListVO> voList = list.stream().map(po -> {
            UserListVO vo = new UserListVO();
            BeanUtils.copyProperties(po, vo);
            vo.setAppName(appId2NameMap.get(po.getAppId()));
            vo.setCreatedByName(userId2NameMap.get(po.getCreatedBy()));
            vo.setUpdatedByName(userId2NameMap.get(po.getUpdatedBy()));
            vo.setRoleList(userId2RoleListMap.get(po.getUserId()));
            vo.setUserId(String.valueOf(po.getUserId()));
            vo.setUpdatedBy(String.valueOf(po.getUpdatedBy()));
            vo.setCreatedBy(String.valueOf(po.getCreatedBy()));

            return vo;
        }).toList();
        return voList;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void setRoles(UserRolesForm form) {
        Long userId = form.getUserId();
        UserPO po = this.getById(userId);
        po.setUpdatedBy(form.getUpdatedBy());
        userRepository.save(po);

        String appId = po.getAppId();
        /**
         * step: 先删除所有，再插入最新
         */
        userRoleRepository.deleteByUserId(userId);
        List<UserRolePO> urList = new ArrayList<>();
        form.getRoleIdList().forEach(roleId -> {
            UserRolePO userRolePO = new UserRolePO();
            userRolePO.setUserId(userId);
            userRolePO.setRoleId(roleId);
            userRolePO.setAppId(appId);
            urList.add(userRolePO);
        });
        userRoleRepository.saveAll(urList);
    }

    @Override
    public UserInfoBO getUserInfoByUserId(Long userId) {
        String key = getTokenUserInfoKey(userId);
        UserInfoBO userInfo = (UserInfoBO) redisTemplate.opsForValue().get(key);
        if (userInfo == null) {
            UserPO userPO = userRepository.findById(userId).orElse(null);
            if (userPO == null) {
                log.error("user not found, userId:{}", userId);
                return null;
            }
            userInfo = new UserInfoBO();
            BeanUtils.copyProperties(userPO, userInfo);
            redisTemplate.opsForValue().set(key, userInfo);
        }
        return userInfo;
    }

    @Override
    public UserPO create(UserCreateForm userCreateForm) {
        String appId = userCreateForm.getAppId();
        String username = userCreateForm.getUsername();
        UserPO userPO = userRepository.findByAppIdAndUsername(appId, username);
        if (userPO != null) {
            throw new AuthException(AuthErrorCode.USER_NAME_EXIST);
        }
        userPO = new UserPO();
        BeanUtils.copyProperties(userCreateForm, userPO);
        Long userId = idUtils.getSnowflakeId();
        userPO.setUserId(userId);
        userPO.setStatus(UserStatus.NORMAL.getValue());
        userPO.setUpdatedBy(userCreateForm.getCreatedBy());
        userPO.setCreatedBy(userCreateForm.getCreatedBy());
        userRepository.save(userPO);
        return this.getById(userId);
    }

    @Override
    public void modifyById(UserModifyForm userModifyForm) {
        Long userId = userModifyForm.getUserId();
        UserPO userPO = this.getById(userId);

        String appId = userPO.getAppId();
        String username = userModifyForm.getUsername();
        String passwordHash = userModifyForm.getPasswordHash();
        String email = userModifyForm.getEmail();
        String phone = userModifyForm.getPhone();
        if (StringUtils.hasText(username)) {
            UserPO target = userRepository.findByAppIdAndUsername(appId, username);
            if (target != null && !target.getUserId().equals(userId)) {
                throw new AuthException(AuthErrorCode.USER_NAME_EXIST);
            }
            userPO.setUsername(username);
        }
        if (StringUtils.hasText(passwordHash)) {
            userPO.setPasswordHash(passwordHash);
        }

        if (StringUtils.hasText(email)) {
            UserPO target = userRepository.findByAppIdAndEmail(appId, email);
            if (target != null && !target.getUserId().equals(userId)) {
                throw new AuthException(AuthErrorCode.USER_EMAIL_EXIST);
            }
            userPO.setEmail(email);
        }

        if (StringUtils.hasText(phone)) {
            UserPO target = userRepository.findByAppIdAndPhone(appId, phone);
            if (target != null && !target.getUserId().equals(userId)) {
                throw new AuthException(AuthErrorCode.USER_PHONE_EXIST);
            }
            userPO.setPhone(phone);
        }
        userPO.setUpdatedBy(userModifyForm.getUpdatedBy());
        userRepository.save(userPO);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeById(Long id) {
        this.getById(id);
        userRepository.deleteById(id);
        userRoleRepository.deleteByUserId(id);
    }

    @Override
    public UserPO getById(Long id) {
        UserPO userPO = userRepository.findById(id).orElse(null);
        if (userPO == null) {
            throw new AuthException(AuthErrorCode.USER_ID_NOT_EXIST);
        }
        return userPO;
    }

    @Override
    public PageResult<UserPO> pageQuery(UserQueryCondition condition) {
        Sort.Direction direction = Sort.Direction.fromString(condition.getDirection());
        Sort sort = Sort.by(direction, condition.getOrderBy());

        /**
         * pageNumber 从0开始
         */
        Integer page = condition.getPage() - 1;
        Integer size = condition.getSize();
        Pageable pageable = PageRequest.of(page, size, sort);
        Specification<UserPO> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (condition.getAppId() != null) {
                predicates.add(cb.equal(root.get("appId"), condition.getAppId()));
            }

            if (condition.getUsername() != null) {
                predicates.add(cb.like(root.get("username"), "%" + condition.getUsername() + "%"));
            }

            if (condition.getEmail() != null) {
                predicates.add(cb.like(root.get("email"), "%" + condition.getEmail() + "%"));
            }
            if (condition.getPhone() != null) {
                predicates.add(cb.like(root.get("phone"), "%" + condition.getPhone() + "%"));
            }

            if (condition.getStatus() != null) {
                predicates.add(cb.equal(root.get("status"), condition.getStatus()));
            }

            if (condition.getKeyword() != null) {
                Predicate usernameMatch = cb.like(root.get("username"), "%" + condition.getKeyword() + "%");
                Predicate emailMatch = cb.like(root.get("email"), "%" + condition.getKeyword() + "%");
                predicates.add(cb.or(usernameMatch, emailMatch)); // 核心修改点
            }
            return cb.and(predicates.toArray(new Predicate[0]));
        };
        Page<UserPO> queryResult = userRepository.findAll(spec, pageable);
        PageResult<UserPO> pageResult = new PageResult(queryResult.getNumber(), queryResult.getSize(), queryResult.getTotalElements(), queryResult.getTotalPages());
        pageResult.setList(queryResult.getContent());
        return pageResult;
    }

    @Override
    public List<UserPO> listAll(UserQueryAllCondition userQueryAllCondition) {
        return List.of();
    }
}
